// Copyright (C) 2010 David Sugar, Tycho Softworks.
//
// This file is part of GNU uCommon C++.
//
// GNU uCommon C++ is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GNU uCommon C++ is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with GNU uCommon C++.  If not, see <http://www.gnu.org/licenses/>.

#include <ucommon/secure.h>
#include <ctype.h>
#include <sys/stat.h>

using namespace UCOMMON_NAMESPACE;

static shell::flagopt helpflag('h',"--help",    _TEXT("display this list"));
static shell::flagopt althelp('?', NULL, NULL);
static shell::numericopt bufopt('b', "--buffer", _TEXT("size of buffer"), "xk");
static shell::flagopt ignore('i', "--ignore", _TEXT("ignore errors"));
static shell::flagopt number('n', "--number", _TEXT("number lines"));

static int exit_code = 0;
static const char *argv0 = "cat";
static char *buffer;
static size_t bufsize = 4096;
static unsigned long lines = 1;
static char nl = '\n', ch = '\n';

static void report(const char *path, int code)
{
    const char *err = _TEXT("i/o error");

    switch(code) {
    case EACCES:
    case EPERM:
        err = _TEXT("permission denied");
        break;
    case EROFS:
        err = _TEXT("read-only file system");
        break;
    case ENODEV:
    case ENOENT:
        err = _TEXT("no such file or directory");
        break;
    case ENOTDIR:
        err = _TEXT("not a directory");
        break;
    case ENOTEMPTY:
        err = _TEXT("directory not empty");
        break;
    case ENOSPC:
        err = _TEXT("no space left on device");
        break;
    case EBADF:
    case ENAMETOOLONG:
        err = _TEXT("bad file path");
        break;
    case EBUSY:
    case EINPROGRESS:
        err = _TEXT("file or directory busy");
        break;
    case EINTR:
        err = _TEXT("operation interupted");
        break;
#ifdef  ELOOP
    case ELOOP:
        err = _TEXT("too many sym links");
        break;
#endif
    }

    if(is(ignore))
        shell::printf(" - %s\n", err);
    else
        shell::errexit(1, "*** %s: %s: %s\n", argv0, path, err);

    exit_code = 1;
}

static void numbering(ssize_t iosize)
{
    ssize_t pos = 0;
    while(pos < iosize) {
        if(ch == nl)
            fprintf(stdout, "%7ld ", lines++);

        ch = buffer[pos++];
        fputc(ch, stdout);
    }
}

static void append(const char *path, fsys_t& output)
{
    ssize_t iosize = bufsize;
    fsys_t file;

    if(path && !eq(path, "-") && !eq(path, "--")) {
        if(fsys::is_dir(path)) {
            fprintf(stderr, "%s: %s: %s\n", argv0, path,
                _TEXT("Is a directory"));
            return;
        }

        file.open(path, fsys::STREAM);
        if(!is(file)) {
            fprintf(stderr, "%s: %s: %s\n", argv0, path,
                _TEXT("Cannot access"));
            return;
        }
    }
    else {
        path = "-";
        file.set(shell::input());
    }

    while(iosize > 0) {
        iosize = file.read(buffer, bufsize);
        if(iosize < 0)
            report(path, file.err());

        if(iosize > 0) {
            if(is(number))
                numbering(iosize);
            else
                output.write(buffer, iosize);
        }
    }
}

PROGRAM_MAIN(argc, argv)
{
    shell::bind("cat");
    shell args(argc, argv);
    argv0 = args.argv0();
    unsigned index = 0;

    argv0 = args.argv0();

    if(is(helpflag) || is(althelp)) {
        printf("%s: %s %s\n", _TEXT("Usage"), argv0, _TEXT("[options] path..."));
        printf("%s\n\n", _TEXT("Append files to standard output"));
        printf("%s\n", _TEXT("Options:"));
        shell::help();
        printf("\n%s\n", _TEXT("Report bugs to dyfet@gnu.org"));
        PROGRAM_EXIT(0);
    }

    if(is(bufopt))
        bufsize = *bufopt * 1024l;

    if(bufsize < 1)
        shell::errexit(2, "*** %s: %ld: %s\n", argv0, (long)bufsize,
            _TEXT("invalid buffer size"));

    buffer = new char[bufsize];
    if(!buffer)
        shell::errexit(2, "*** %s: %ld: %s\n", argv0, (long)bufsize,
            _TEXT("cannot allocate buffer"));

    fsys_t output(shell::output());

    if(!args())
        append(NULL, output);

    while(index < args())
        append(args[index++], output);

    if(is(number))
        fflush(stdout);

    PROGRAM_EXIT(exit_code);
}

